package net.brucecooper.wave.bottest;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.text.DateFormat;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import com.google.gdata.client.calendar.CalendarService;
import com.google.gdata.data.DateTime;
import com.google.gdata.data.Person;
import com.google.gdata.data.PlainTextConstruct;
import com.google.gdata.data.extensions.EventEntry;
import com.google.gdata.data.extensions.When;
import com.google.gdata.util.ServiceException;
import com.google.wave.api.AbstractRobotServlet;
import com.google.wave.api.Blip;
import com.google.wave.api.Element;
import com.google.wave.api.ElementType;
import com.google.wave.api.Event;
import com.google.wave.api.EventType;
import com.google.wave.api.FormElement;
import com.google.wave.api.FormView;
import com.google.wave.api.Range;
import com.google.wave.api.RobotMessageBundle;
import com.google.wave.api.StyleType;
import com.google.wave.api.StyledText;
import com.google.wave.api.TextView;
import com.google.wave.api.Wavelet;

enum LeaveType {
    leaveType_personal("Sick/Personal"), leaveType_annual("Annual"), leaveType_maternity("Maternity"), leaveType_carers("Carers"), leaveType_study(
            "Study");

    String pretty;

    private LeaveType(String val) {
        this.pretty = val;
    }

    public String toPrettyString() {
        return pretty;
    }
};

class FormData {
    Date startDate;
    Number numDays;
    String applicant;
    LeaveType leaveType;
}

@SuppressWarnings("serial")
public class WaveBotServlet extends AbstractRobotServlet {

    private String getToday() {
        DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT);

        return df.format(new Date());

    }

    private FormData getForm(FormView form) throws ProcessingException {
        FormData fd = new FormData();

        try {
            String leaveTypeStr = form.getFormElement("leaveType").getValue();
            fd.startDate = DateFormat.getDateInstance(DateFormat.SHORT).parse(form.getFormElement("startdate").getValue());
            fd.applicant = form.getFormElement("applicant").getValue();
            fd.numDays = NumberFormat.getInstance().parse(form.getFormElement("days").getValue());

            if (leaveTypeStr == null || leaveTypeStr.equals("")) {
                throw new ProcessingException("You have not selected a leave type");
            }
            fd.leaveType = LeaveType.valueOf(leaveTypeStr);
        } catch (ParseException e1) {
            throw new ProcessingException(e1.getMessage());
        }

        return fd;
    }

    @Override
    public void processEvents(RobotMessageBundle bundle) {
        Wavelet wavelet = bundle.getWavelet();

        try {
            if (bundle.wasSelfAdded()) {
                Blip root = wavelet.getRootBlip();
                TextView textView = root.getDocument();
                FormView form = textView.getFormView();
                textView.append("Leave Form - New\n");

                textView
                        .append("Welcome to the Unico leave request system.  This wave is intended to be a discussion between, you, your manager, and the HR department.  For more information, see the ");
                textView
                        .appendMarkup("<a href=\"https://wave.google.com/wave/?pli=1#restored:wave:googlewave.com!w%2BYa9NBvSAG\">Unico Leave Application Procedure</a>. ");
                textView
                        .append("If you want to add additional information to the application, simply add it to this wave in the main blip, or as a reply.");

                textView.append("\n\n");
                textView.appendStyledText(new StyledText("Applicant: \n", StyleType.BOLD));
                form.append(new FormElement(ElementType.INPUT, "applicant", wavelet.getCreator()));
                textView.appendStyledText(new StyledText("\nLeave Type:", StyleType.BOLD));

                textView.appendElement(new FormElement(ElementType.RADIO_BUTTON_GROUP, "leaveType", "leaveType_personal"));

                for (LeaveType leaveType : LeaveType.values()) {
                    textView.append("\n\t");
                    textView.appendElement(new FormElement(ElementType.RADIO_BUTTON, "leaveType", leaveType.name()));
                    textView.appendElement(new FormElement(ElementType.LABEL, leaveType.name(), leaveType.toPrettyString()));
                }
                textView.append("\n");

                textView.appendStyledText(new StyledText("Leave Date: ", StyleType.BOLD));
                form.append(new FormElement(ElementType.INPUT, "startdate", getToday()));
                textView.appendStyledText(new StyledText("Number of Days: ", StyleType.BOLD));
                form.append(new FormElement(ElementType.INPUT, "days", "1"));
                textView.appendStyledText(new StyledText("Form Actions:", StyleType.BOLD));
                textView.append("  (note that the bot will check that you are allowed to perform actions)\n");
                form.append(new FormElement(ElementType.BUTTON, "Submit", "Submit"));

                Blip blip = wavelet.appendBlip();
                textView = blip.getDocument();
                textView
                        .append("If you would like to talk to the leave-bot, reply to this blip, reply to this message or create a new blip, but address it to the bot by saying \"bot, what is my leave balance\" or somesuch.");
            }

            for (Event e : bundle.getEvents()) {
                if (e.getType() == EventType.WAVELET_PARTICIPANTS_CHANGED) {
                    // Blip blip = wavelet.appendBlip();
                    // TextView textView = blip.getDocument();
                    // textView.append("Hi, everybody!");
                }
                if (e.getType() == EventType.FORM_BUTTON_CLICKED) {
                    String buttonName = e.getButtonName();

                    Blip blip = wavelet.appendBlip();
                    TextView textView = blip.getDocument();
                    textView.append("User " + e.getModifiedBy() + " Clicked on the " + buttonName + " button");

                    TextView doc = wavelet.getRootBlip().getDocument();

                    String[] newActions = new String[0];
                    String methodName = "action" + buttonName;
                    try {

                        Method m = this.getClass().getMethod(methodName, Event.class, Blip.class);
                        newActions = (String[]) m.invoke(this, e, blip);

                        // TODO for some bizarre reason, the Wavelet api shits
                        // itself if there are three buttons,
                        // unless you go from the end backwards.. I should
                        // report this to google.
                        List<Element> existingButtons;
                        while (!(existingButtons = doc.getElements(ElementType.BUTTON)).isEmpty()) {
                            FormElement fe = (FormElement) existingButtons.get(existingButtons.size() - 1);
                            doc.getFormView().delete(fe);
                        }
                        // Add new Buttons.
                        for (String newAction : newActions) {
                            doc.getFormView().append(new FormElement(ElementType.BUTTON, newAction, newAction));
                        }
                        String postfix = buttonName.equals("Withdraw") ? "n" : "ed";
                        wavelet.setTitle("Leave Form - " + buttonName + postfix);

                    } catch (NoSuchMethodException e1) {
                        textView.append("\nNo action method called " + methodName + " in class " + this.getClass().getName()
                                + "  Ignoring");
                    } catch (SecurityException e1) {
                        log("Security exception while actioning " + buttonName, e1);
                    } catch (IllegalArgumentException e1) {
                        log("Security exception while actioning " + buttonName, e1);
                    } catch (IllegalAccessException e1) {
                        log("Security exception while actioning " + buttonName, e1);
                    } catch (InvocationTargetException e1) {
                        if (e1.getCause() instanceof PermissionException) {
                            PermissionException pex = (PermissionException) e1.getCause();
                            textView.append("\nUser " + e.getModifiedBy() + " is not permitted to use the " + buttonName
                                    + " action: " + pex.getMessage() + ".  Ignoring");
                        } else if (e1.getCause() instanceof ProcessingException) {
                            ProcessingException pex = (ProcessingException) e1.getCause();
                            textView.append("\nThere was a problem with the form, which meant it could not be submitted: "
                                    + pex.getMessage() + ".  Please correct the error and try aain");
                        } else {
                            log("Unknown exception while actioning " + buttonName, e1);
                        }
                    }
                } else if (e.getType() == EventType.DOCUMENT_CHANGED) {
                    // Blip blip = wavelet.appendBlip();
                    // TextView textView = blip.getDocument();
                    // textView.append("Doc Changed!");
                } else if (e.getType() == EventType.BLIP_SUBMITTED) {
                    String blipText = e.getBlip().getDocument().getText();
                    if (isCommand(blipText)) {
                        Blip response = e.getBlip().createChild();
                        TextView textView = response.getDocument();
                        textView.append("Your leave balance is 20 days, as at " + new Date());

                    }

                } else if (e.getType() == EventType.WAVELET_BLIP_CREATED) {
                    // Blip blip = wavelet.appendBlip();
                    // TextView textView = blip.getDocument();
                    // textView.append("Wavelet Blip Created!");

                }
            }

        } catch (Throwable ex) {
            log("Caught Exception: ", ex);
            Blip exceptionBlip = wavelet.appendBlip();
            TextView textView = exceptionBlip.getDocument();
            textView.append("whoops, something went wrong.....\n");
            StringWriter sw = new StringWriter();
            ex.printStackTrace(new PrintWriter(sw));

            textView.append(sw.toString());
        }
    }

    private boolean isCommand(String blipText) {
        String lc = blipText.toLowerCase().trim();
        return lc.startsWith("bot,") || lc.startsWith("robot,") || lc.startsWith("leave bot,") || lc.startsWith("#");
    }

    public String getManager(String user) {
        if (user.equalsIgnoreCase("brucejcooper@googlewave.com")) {
            return "dlimiter@googlewave.com";
        } else {
            return "brucejcooper@googlewave.com";
        }
    }

    public void checkApprovalRights(Wavelet wave, String user) throws PermissionException {
        String manager = getManager(wave.getCreator());
        if (!(user.equalsIgnoreCase(manager) || user.equalsIgnoreCase("brucejcooper@googlewave.com"))) {
            throw new PermissionException("Only " + manager + " or part of the leadership group can approve/reject the leave form");
        }

    }

    public void checkProcessRights(Wavelet wave, String user) throws PermissionException {
        // TODO to be implemented
    }

    public String[] actionSubmit(Event e, Blip blip) throws ProcessingException, IOException, ServiceException {
        blip.getDocument().append("\nSubmitting Request");

        // Check the form for parse errors
        getForm(blip.getWavelet().getRootBlip().getDocument().getFormView());

        String manager = getManager(blip.getWavelet().getCreator());

        if (blip.getWavelet().getParticipants().contains(manager)) {
            blip.getDocument().append(
                    '\n' + blip.getWavelet().getCreator() + "'s manager is " + manager + ", but he's already on the wave.");
        } else {
            blip.getDocument().append(
                    '\n' + blip.getWavelet().getCreator() + "'s manager is " + manager + ".  Adding him to the wave");
        }
        blip.getWavelet().addParticipant(manager);

        return new String[] { "Approve", "Reject", "Withdraw" };
    }

    public void addToCalendar(FormData form) throws IOException, ServiceException {
        URL feedUrl = new URL("http://www.google.com/calendar/feeds/leaveapplication@brucecooper.net/private/full");
        CalendarService calService = new CalendarService("brucecooper.net-wavebot-1.0");
        calService.setUserCredentials("leaveapplication@brucecooper.net", "putpasswordhere");

        EventEntry myEntry = new EventEntry();

        myEntry.setTitle(new PlainTextConstruct(form.leaveType.toPrettyString() + " for " + form.applicant));
        myEntry.setContent(new PlainTextConstruct("Leave Entry created by Google Wave bot.."));

        Person author = new Person(form.applicant, null, form.applicant);
        myEntry.getAuthors().add(author);

        Calendar cal = Calendar.getInstance();
        cal.setTime(form.startDate);
        DateTime startTime = new DateTime(cal.getTime());
        startTime.setDateOnly(true);

        cal.add(Calendar.DAY_OF_YEAR, form.numDays.intValue());
        DateTime endTime = new DateTime(cal.getTime());
        endTime.setDateOnly(true);

        System.out.println("Goes from " + startTime + " to " + endTime);
        When eventTimes = new When();
        eventTimes.setStartTime(startTime);
        eventTimes.setEndTime(endTime);
        myEntry.addTime(eventTimes);

        EventEntry insertedEntry = calService.insert(feedUrl, myEntry);
    }

    public String[] actionReject(Event e, Blip blip) throws PermissionException {
        checkApprovalRights(e.getWavelet(), e.getModifiedBy());
        blip.getDocument().append("\nRejecting Request");
        return new String[] { "Submit" };
    }

    public String[] actionWithdraw(Event e, Blip blip) throws PermissionException {

        blip.getDocument().append("\nWithdrawing Request");
        return new String[] { "Submit" };
    }

    public String[] actionApprove(Event e, Blip blip) throws IOException, ServiceException, ProcessingException {
        checkApprovalRights(e.getWavelet(), e.getModifiedBy());
        
        TextView txt = blip.getDocument();
        txt.append("\nApproving Request, and adding to leave calendar available ");
        int pos = txt.getText().length();
        txt.append("Here");
        txt.setAnnotation(new Range(pos, pos+4), "link/manual", "http://www.google.com/calendar/embed?src=leaveapplication@brucecooper.net&ctz=Australia/Sydney");

        addToCalendar(getForm(e.getWavelet().getRootBlip().getDocument().getFormView()));
        return new String[] { "Process", "Withdraw" };
    }

    public String[] actionProcess(Event e, Blip blip) throws PermissionException {
        checkProcessRights(e.getWavelet(), e.getModifiedBy());
        blip.getDocument().append("\nApproving Request");
        return new String[] { "Withdraw" };
    }
}
